﻿// -----------------------------------------------------------------------
// <copyright file="SubscriptionRepository.cs" company="Zen Design Corp">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace Zen.BlackBerry.Push.Repositories
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Runtime.Caching;
	using Microsoft.Practices.ServiceLocation;
	using Zen.BlackBerry.Push.DataAccess;
	using Zen.BlackBerry.Subscription;
	using Zen.BlackBerry.Subscription.Count;
	using Zen.BlackBerry.Subscription.Web;
	using Zen.Data.Extensions;

	/// <summary>
	/// TODO: Update summary.
	/// </summary>
	public class SubscriptionRepository : CachingRepository, ISubscriptionRepository
	{
		public SubscriptionRepository()
		{
		}

		private IEnumerable<SubscriberPartial> HydrateResults(IQueryable<DbPushSubscriber> query, bool fullObject = false)
		{
			if (!fullObject)
			{
				return query.Select((item) =>
					new SubscriberPartial
					{
						Address = item.Address,
						PushApplicationId = item.Application.RimApplicationId,
						Status = (SubscriberStatus)item.Status,
						SubscriberId = item.SubscriberId,
						Type = (SubscriberType)item.Type
					});
			}
			else
			{
				return query.Select((item) =>
					new Subscriber
					{
						Address = item.Address,
						PushApplicationId = item.Application.RimApplicationId,
						Status = (SubscriberStatus)item.Status,
						SubscriberId = item.SubscriberId,
						Type = (SubscriberType)item.Type,
						Model = item.Model,
						OsVersion = item.OsVersion,
						SubscribeDate = item.SubscribeDate,
						SuspendDate = item.SuspendDate,
						ResumeDate = item.ResumeDate,
						UnsubscribeDate = item.UnsubscribeDate
					});
			}
		}

		#region ISubscriptionRepository Members

		public void DeleteInactiveSubscription(string pushApplicationId, string subscriberId)
		{
			DbPushSubscriber dbSub = ObjectContext.DbPushSubscribers
				.Include("Application")
				.FirstOrDefault((item) =>
					item.Application.RimApplicationId == pushApplicationId &&
					item.SubscriberId == subscriberId);
			if (dbSub == null || dbSub.Status == (short)SubscriberStatus.Inactive)
			{
				ObjectContext.DbPushSubscribers.DeleteObject(dbSub);
				ObjectContext.SaveChanges();
			}
		}

		public void DeleteSubscriptions(string pushApplicationId)
		{
			ObjectContext.ExecuteStoreCommand("bps_DeleteSubscriptions(?)", pushApplicationId);
		}

		public IEnumerable<SubscriberPartial> FindByAddressAndIdAndNotStatus(string address, string subscriberId, SubscriberStatus status)
		{
			IQueryable<DbPushSubscriber> query = ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.Address == address && item.SubscriberId == subscriberId && item.Status != (short)status);
			return HydrateResults(query);
		}

		public IEnumerable<SubscriberPartial> FindByAddressAndNotIdAndNotStatus(string address, string subscriberId, SubscriberStatus status)
		{
			IQueryable<DbPushSubscriber> query = ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.Address == address && item.SubscriberId != subscriberId && item.Status != (short)status);
			return HydrateResults(query);
		}

		public IEnumerable<SubscriberPartial> FindByAppId(string pushApplicationId, int startIndex, int endIndex)
		{
			IQueryable<DbPushSubscriber> query = ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.Application.RimApplicationId == pushApplicationId)
				.Skip(startIndex)
				.Take(endIndex - startIndex + 1);
			return HydrateResults(query);
		}

		public IEnumerable<SubscriberPartial> FindByAppIdAndAddress(string pushApplicationId, string address)
		{
			IQueryable<DbPushSubscriber> query = ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.Application.RimApplicationId == pushApplicationId && item.Address == address);
			return HydrateResults(query);
		}

		public Subscriber FindByAppIdAndId(string pushApplicationId, string subscriberId)
		{
			IQueryable<DbPushSubscriber> query = ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.Application.RimApplicationId == pushApplicationId && item.SubscriberId == subscriberId);
			return HydrateResults(query, true)
				.OfType<Subscriber>()
				.FirstOrDefault();
		}

		public SubscriberPartial FindByAppIdAndIdPartial(string pushApplicationId, string subscriberId)
		{
			IQueryable<DbPushSubscriber> query = ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.Application.RimApplicationId == pushApplicationId && item.SubscriberId == subscriberId);
			return HydrateResults(query, false)
				.OfType<SubscriberPartial>()
				.FirstOrDefault();
		}

		public IEnumerable<SubscriberPartial> FindByAppIdAndIdsAndStatus(string pushApplicationId, IEnumerable<string> subscriberIds, SubscriberStatus status)
		{
			IQueryable<DbPushSubscriber> query = ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.Application.RimApplicationId == pushApplicationId && item.Status == (short)status)
				.ContainsEx((item) => item.SubscriberId, subscriberIds);
			return HydrateResults(query);
		}

		public IEnumerable<SubscriberPartial> FindByAppIdAndStatus(string pushApplicationId, SubscriberStatus status, int startIndex, int endIndex)
		{
			IQueryable<DbPushSubscriber> query = ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.Application.RimApplicationId == pushApplicationId && item.Status == (short)status)
				.Skip(startIndex)
				.Take(endIndex - startIndex + 1);
			return HydrateResults(query);
		}

		public IEnumerable<SubscriberPartial> FindByAppIdAndType(string pushApplicationId, SubscriberType type, int startIndex, int endIndex)
		{
			IQueryable<DbPushSubscriber> query = ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.Application.RimApplicationId == pushApplicationId && item.Type == (short)type)
				.Skip(startIndex)
				.Take(endIndex - startIndex + 1);
			return HydrateResults(query);
		}

		public IEnumerable<SubscriberPartial> FindByAppIdAndTypeAndStatus(string pushApplicationId, SubscriberType type, SubscriberStatus status, int startIndex, int endIndex)
		{
			IQueryable<DbPushSubscriber> query = ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.Application.RimApplicationId == pushApplicationId && item.Type == (short)type && item.Status == (short)status)
				.Skip(startIndex)
				.Take(endIndex - startIndex + 1);
			return HydrateResults(query);
		}

		public IEnumerable<SubscriberPartial> FindById(string subscriberId)
		{
			IQueryable<DbPushSubscriber> query = ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.SubscriberId == subscriberId);
			return HydrateResults(query);
		}

		public IEnumerable<SubscriberPartial> FindByIdAndNotStatus(string subscriberId, SubscriberStatus status)
		{
			IQueryable<DbPushSubscriber> query = ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.SubscriberId == subscriberId && item.Status != (short)status);
			return HydrateResults(query);
		}

		public IEnumerable<SubscriberPartial> FindByIdAndStatus(string subscriberId, SubscriberStatus status)
		{
			IQueryable<DbPushSubscriber> query = ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.SubscriberId == subscriberId && item.Status == (short)status);
			return HydrateResults(query);
		}

		public IEnumerable<SubscriberPartial> FindByIdPattern(string subscriberIdPattern, int startIndex, int endIndex)
		{
			IQueryable<DbPushSubscriber> query = ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.SubscriberId.Contains(subscriberIdPattern))
				.Skip(startIndex)
				.Take(endIndex - startIndex + 1);
			return HydrateResults(query);
		}

		public IEnumerable<DeviceModelCount> GetDeviceModelCount(string pushApplicationId)
		{
			return ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.Application.RimApplicationId == pushApplicationId)
				.GroupBy((item) => item.Model)
				.Select((item) =>
					new DeviceModelCount
					{
						Model = item.Key,
						Count = item.Count()
					});
		}

		public IEnumerable<DeviceModelCount> GetDeviceModelCount(string pushApplicationId, SubscriberStatus status)
		{
			return ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.Application.RimApplicationId == pushApplicationId && item.Status == (short)status)
				.GroupBy((item) => item.Model)
				.Select((item) =>
					new DeviceModelCount
					{
						Model = item.Key,
						Count = item.Count()
					});
		}

		public IEnumerable<OsVersionCount> GetOsVersionCount(string pushApplicationId)
		{
			return ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.Application.RimApplicationId == pushApplicationId)
				.GroupBy((item) => item.Model)
				.Select((item) =>
					new OsVersionCount
					{
						OsVersion = item.Key,
						Count = item.Count()
					});
		}

		public IEnumerable<OsVersionCount> GetOsVersionCount(string pushApplicationId, SubscriberStatus status)
		{
			return ObjectContext.DbPushSubscribers
				.Include("Application")
				.Where((item) => item.Application.RimApplicationId == pushApplicationId && item.Status == (short)status)
				.GroupBy((item) => item.Model)
				.Select((item) =>
					new OsVersionCount
					{
						OsVersion = item.Key,
						Count = item.Count()
					});
		}

		public int GetSubscribeCount(string pushApplicationId, DateTime fromDate, DateTime toDate)
		{
			return ObjectContext.DbPushSubscribers
				.Include("Application")
				.Count((item) => item.Application.RimApplicationId == pushApplicationId && item.SubscribeDate != null && item.SubscribeDate >= fromDate && item.SubscribeDate <= toDate);
		}

		public int GetSuspendCount(string pushApplicationId, DateTime fromDate, DateTime toDate)
		{
			return ObjectContext.DbPushSubscribers
				.Include("Application")
				.Count((item) => item.Application.RimApplicationId == pushApplicationId && item.SuspendDate != null && item.SuspendDate >= fromDate && item.SuspendDate <= toDate);
		}

		public int GetResumeCount(string pushApplicationId, DateTime fromDate, DateTime toDate)
		{
			return ObjectContext.DbPushSubscribers
				.Include("Application")
				.Count((item) => item.Application.RimApplicationId == pushApplicationId && item.ResumeDate != null && item.ResumeDate >= fromDate && item.ResumeDate <= toDate);
		}

		public int GetUnsubscribeCount(string pushApplicationId, DateTime fromDate, DateTime toDate)
		{
			return ObjectContext.DbPushSubscribers
				.Include("Application")
				.Count((item) => item.Application.RimApplicationId == pushApplicationId && item.UnsubscribeDate != null && item.UnsubscribeDate >= fromDate && item.UnsubscribeDate <= toDate);
		}

		public int GetSizeByAppId(string pushApplicationId)
		{
			return ObjectContext.DbPushSubscribers
				.Include("Application")
				.Count((item) => item.Application.RimApplicationId == pushApplicationId);
		}

		public int GetSizeByAppIdAndStatus(string pushApplicationId, SubscriberStatus status)
		{
			return ObjectContext.DbPushSubscribers
				.Include("Application")
				.Count((item) => item.Application.RimApplicationId == pushApplicationId && item.Status == (short)status);
		}

		public int GetSizeByAppIdAndType(string pushApplicationId, SubscriberType type)
		{
			return ObjectContext.DbPushSubscribers
				.Include("Application")
				.Count((item) => item.Application.RimApplicationId == pushApplicationId && item.Type == (short)type);
		}

		public int GetSizeByAppIdAndTypeAndStatus(
			string pushApplicationId, SubscriberType type, SubscriberStatus status)
		{
			return ObjectContext.DbPushSubscribers
				.Include("Application")
				.Count((item) => item.Application.RimApplicationId == pushApplicationId && item.Type == (short)type && item.Status == (short)status);
		}

		public int GetSizeByIdPattern(string subscriberIdPattern)
		{
			return ObjectContext.DbPushSubscribers.Count((item) =>
				item.SubscriberId.Contains(subscriberIdPattern));
		}

		public SubscriberPartial LoadFromCache(
			string pushApplicationId, string subscriberId)
		{
			SubscriberPrimaryKey key =
				new SubscriberPrimaryKey(pushApplicationId, subscriberId);
			return GetCachedItem<SubscriberPartial>(key.ToString());
		}

		public void Resubscribe(SubscribeRequest subscribeRequest)
		{
			DbPushSubscriber dbSub = ObjectContext.DbPushSubscribers
				.Include("Application")
				.FirstOrDefault((item) =>
					item.Application.RimApplicationId == subscribeRequest.PushApplicationId &&
					item.SubscriberId == subscribeRequest.SubscriberId);
			if (dbSub == null)
			{
				DbPushApplication dbApp = ObjectContext.DbPushApplications
					.First((item) => item.RimApplicationId == subscribeRequest.PushApplicationId);
				dbSub =
					new DbPushSubscriber
					{
						PushSubscriberId = Guid.NewGuid(),
						Application = dbApp,
					};
				ObjectContext.DbPushSubscribers.AddObject(dbSub);
			}
			else
			{
				dbSub.SuspendDate = null;
				dbSub.ResumeDate = null;
				dbSub.UnsubscribeDate = null;
			}

			dbSub.Model = subscribeRequest.Model;
			dbSub.OsVersion = subscribeRequest.OsVersion;
			dbSub.Type = (short)subscribeRequest.Type;
			dbSub.Address = subscribeRequest.Address;
			dbSub.SubscribeDate = DateTime.UtcNow;
			dbSub.Status = (short)SubscriberStatus.Active;
			ObjectContext.SaveChanges();
		}

		public void Resume(ResumeRequest resumeRequest)
		{
			DbPushSubscriber dbSub = ObjectContext.DbPushSubscribers
				.Include("Application")
				.FirstOrDefault((item) =>
					item.Application.RimApplicationId == resumeRequest.PushApplicationId &&
					item.SubscriberId == resumeRequest.SubscriberId &&
					item.Status == (short)SubscriberStatus.Suspended);
			if (dbSub != null)
			{
				dbSub.ResumeDate = DateTime.UtcNow;
				dbSub.SuspendDate = null;
				dbSub.Status = (short)SubscriberStatus.Active;
				ObjectContext.SaveChanges();
			}
		}

		public void Subscribe(SubscribeRequest subscribeRequest)
		{
			DbPushSubscriber dbSub = ObjectContext.DbPushSubscribers
				.Include("Application")
				.FirstOrDefault((item) =>
					item.Application.RimApplicationId == subscribeRequest.PushApplicationId &&
					item.SubscriberId == subscribeRequest.SubscriberId);
			if (dbSub == null)
			{
				DbPushApplication dbApp = ObjectContext.DbPushApplications
					.First((item) => item.RimApplicationId == subscribeRequest.PushApplicationId);
				dbSub =
					new DbPushSubscriber
					{
						PushSubscriberId = Guid.NewGuid(),
						Application = dbApp,
					};
				ObjectContext.DbPushSubscribers.AddObject(dbSub);
			}
			else
			{
				dbSub.SuspendDate = null;
				dbSub.ResumeDate = null;
				dbSub.UnsubscribeDate = null;
			}

			dbSub.Model = subscribeRequest.Model;
			dbSub.OsVersion = subscribeRequest.OsVersion;
			dbSub.Type = (short)subscribeRequest.Type;
			dbSub.Address = subscribeRequest.Address;
			dbSub.SubscribeDate = DateTime.UtcNow;
			dbSub.Status = (short)SubscriberStatus.Active;
			ObjectContext.SaveChanges();
		}

		public void Suspend(SuspendRequest suspendRequest)
		{
			DbPushSubscriber dbSub = ObjectContext.DbPushSubscribers
				.Include("Application")
				.FirstOrDefault((item) =>
					item.Application.RimApplicationId == suspendRequest.PushApplicationId &&
					item.SubscriberId == suspendRequest.SubscriberId &&
					item.Status == (short)SubscriberStatus.Active);
			if (dbSub != null)
			{
				dbSub.SuspendDate = DateTime.UtcNow;
				dbSub.ResumeDate = null;
				dbSub.SubscribeDate = null;
				dbSub.Status = (short)SubscriberStatus.Suspended;
				ObjectContext.SaveChanges();
			}
		}

		public void Unsubscribe(UnsubscribeRequest unsubscribeRequest)
		{
			DbPushSubscriber dbSub = ObjectContext.DbPushSubscribers
				.Include("Application")
				.FirstOrDefault((item) =>
					item.Application.RimApplicationId == unsubscribeRequest.PushApplicationId &&
					item.SubscriberId == unsubscribeRequest.SubscriberId &&
					item.Status != (short)SubscriberStatus.Inactive);
			if (dbSub != null)
			{
				dbSub.UnsubscribeDate = DateTime.UtcNow;
				dbSub.ResumeDate = null;
				dbSub.SuspendDate = null;
				dbSub.SubscribeDate = null;
				dbSub.Status = (short)SubscriberStatus.Inactive;
				ObjectContext.SaveChanges();
			}
		}
		#endregion
	}
}
